#include <stdio.h>
#include "game_platform.h"
#include "game_debug.h"
#include "game_debug_ui.h"
#include "game_asset.h"
#include "game_render_group.h"
#include "game_asset.h"
#include "game_debug_variable.h"
#include "game_config.h"
#include "game_sort.h"
#include "game_shared.h"

debug_table *GlobalDebugTable = 0;

font_id GetBestMatchFontFrom(game_assets *Asset, assets_type_id TypeID, asset_vector *MatchVector,
                             asset_vector *WeightVector);

inline uint32 GetLaneFromThreadIndex(debug_state *DebugState, uint32 ThreadIndex) {
    (void)DebugState;
    (void)ThreadIndex;
    uint32 Result = 0;
    return Result;
}

internal debug_thread *GetDebugThread(debug_state *DebugState, uint32 ThreadID) {
    debug_thread *Result = 0;
    for (debug_thread *Thread = DebugState->FirstThread; Thread; Thread = Thread->Next) {
        if (Thread->ID == ThreadID) {
            Result = Thread;
            break;
        }
    }
    if (!Result) {
        FREELIST_ALLOC(Result, DebugState->FirstFreeThread,
                       PushStruct(&DebugState->DebugArena, debug_thread));
        Result->ID = ThreadID;
        Result->FirstOpenCodeBlock = 0;
        Result->FirstOpenDataBlock = 0;
        Result->LaneIndex = DebugState->FrameBarLaneCount++;
        Result->Next = DebugState->FirstThread;
        DebugState->FirstThread = Result;
    }
    return Result;
}

inline open_debug_block *AllocateOpenDebugBlock(debug_state *DebugState, debug_element *Element,
                                                uint32 FrameIndex, debug_event *Event,
                                                open_debug_block **FirstOpenBlock) {
    open_debug_block *Result = 0;
    FREELIST_ALLOC(Result, DebugState->FirstFreeBlock,
                   PushStruct(&DebugState->DebugArena, open_debug_block));

    Result->StartingFrameIndex = FrameIndex;
    Result->BeginClock = Event->Clock;
    Result->NextFree = 0;
    Result->Element = Element;

    Result->Parent = *FirstOpenBlock;
    *FirstOpenBlock = Result;

    return Result;
}

inline void DeallocateOpenDebugBlock(debug_state *DebugState, open_debug_block **FirstOpenBlock) {
    open_debug_block *FreeBlock = *FirstOpenBlock;
    *FirstOpenBlock = FreeBlock->Parent;

    FreeBlock->NextFree = DebugState->FirstFreeBlock;
    DebugState->FirstFreeBlock = FreeBlock;
}

inline bool32 EventMatch(debug_event A, debug_event B) {
    bool Result = (A.ThreadID == B.ThreadID);
    return Result;
}
internal debug_variable_link *AddLinkToGroup(debug_state *DebugState, debug_variable_link *Parent,
                                             debug_variable_link *Link);
internal debug_variable_link *CreateVariableLink(debug_state *DebugState, uint32 NameLength,
                                                 const char *Name);

internal debug_variable_link *GetOrCreateGroupWithName(debug_state *DebugState,
                                                       debug_variable_link *Parent,
                                                       uint32 NameLength, const char *Name) {
    debug_variable_link *Result = 0;
    for (debug_variable_link *Link = Parent->FirstChild; Link != GetSentinel(Parent);
         Link = Link->Next) {
        if (HasChildren(Link) && StringsAreEqual(NameLength, Name, Link->Name)) {
            Result = Link;
        }
    }
    if (!Result) {
        Result = CreateVariableLink(DebugState, NameLength, Name);
        AddLinkToGroup(DebugState, Parent, Result);
    }
    return Result;
}

internal debug_variable_link *GetGroupForHierarchicalName(debug_state *DebugState,
                                                          debug_variable_link *Parent,
                                                          const char *BlockName,
                                                          bool32 CreateTerminal = false) {
    (void)DebugState;
    (void)BlockName;

    debug_variable_link *Result = Parent;
    const char *FirstSearator = 0;
    const char *Scan = BlockName;
    for (; *Scan; ++Scan) {
        if (*Scan == '/') {
            FirstSearator = Scan;
            break;
        }
    }
    if (FirstSearator || CreateTerminal) {
        uint32 NameLength = 0;
        if (FirstSearator) {
            NameLength = (uint32)(FirstSearator - BlockName);
        } else {
            NameLength = (uint32)(Scan - BlockName);
        }
        Result = GetOrCreateGroupWithName(DebugState, Parent, NameLength, BlockName);
        if (FirstSearator) {
            Result =
                GetGroupForHierarchicalName(DebugState, Result, FirstSearator + 1, CreateTerminal);
        }
    }

    return Result;
}

internal debug_variable_link *AddElementToGroup(debug_state *DebugState,
                                                debug_variable_link *Parent,
                                                debug_element *Element) {
    debug_variable_link *Link = CreateVariableLink(DebugState, 0, 0);
    if (Parent) {
        DLIST_INSERT_AT_LAST(GetSentinel(Parent), Link);
    }
    Link->Element = Element;
    return Link;
}

internal debug_variable_link *AddLinkToGroup(debug_state *DebugState, debug_variable_link *Parent,
                                             debug_variable_link *Link) {
    (void)DebugState;
    DLIST_INSERT_AT_LAST(GetSentinel(Parent), Link);
    return Link;
}

internal debug_variable_link *CreateVariableLink(debug_state *DebugState, uint32 NameLength,
                                                 const char *Name) {
    debug_variable_link *Link = PushStruct(&DebugState->DebugArena, debug_variable_link);
    DLIST_INIT(GetSentinel(Link));
    Link->Next = Link->Prev = 0;
    Link->Name = NameLength ? PushAndNullTerminate(&DebugState->DebugArena, NameLength, Name) : 0;
    return Link;
}

internal debug_variable_link *CloneVariableLink(debug_state *DebugState,
                                                debug_variable_link *DestGroup,
                                                debug_variable_link *Source) {
    debug_variable_link *Dest = AddElementToGroup(DebugState, DestGroup, Source->Element);
    Dest->Name = Source->Name;
    if (HasChildren(Source)) {
        for (debug_variable_link *Child = Source->FirstChild;  //
             Child != GetSentinel(Source);                     //
             Child = Child->Next) {
            CloneVariableLink(DebugState, Dest, Child);
        }
    }
    return Dest;
}

internal debug_variable_link *CloneVariableLink(debug_state *DebugState,
                                                debug_variable_link *Source) {
    debug_variable_link *Result = CloneVariableLink(DebugState, 0, Source);
    return Result;
}

internal void FreeFrame(debug_state *DebugState, uint32 FrameOrdinal) {
    Assert(FrameOrdinal < DEBUG_FRAME_COUNT);
    uint32 FreeEventCount = 0;
    for (uint32 ElementHashIndex = 0; ElementHashIndex < ArrayCount(DebugState->ElementsHash);
         ++ElementHashIndex) {
        for (debug_element *Element = DebugState->ElementsHash[ElementHashIndex]; Element;
             Element = Element->NextInHash) {
            debug_element_frame *ElementFrame = Element->Frames + FrameOrdinal;

            while (ElementFrame->OldestEvent) {
                debug_stored_event *FreeEvent = ElementFrame->OldestEvent;
                ElementFrame->OldestEvent = FreeEvent->Next;
                FREELIST_DEALLOCATE(FreeEvent, DebugState->FirstFreeStoredEvent);
                ++FreeEventCount;
            }
            ZeroStruct(*ElementFrame);
        }
    }
    debug_frame *Frame = DebugState->Frames + FrameOrdinal;
    Assert(Frame->StoreEventCount == FreeEventCount);
    ZeroStruct(*Frame);
}

internal void InitFrame(debug_state *DebugState, uint64 BeginClock, debug_frame *Result) {
    ZeroStruct(*Result);
    Result->FrameIndex = DebugState->TotalFrameCount++;
    Result->FrameBarScale = 1.0f;
    Result->BeginClock = BeginClock;
}

internal void FreeVariableGroup(debug_state *DebugState, debug_variable_link *Group) {
    (void)DebugState;
    (void)Group;
    Assert(!"Not implemented");
}

struct debug_string {
    uint32 Length;
    const char *Value;
};

inline const char *GetName(debug_element *Element) {
    const char *Result = Element->GUID + Element->NameStartsAt;
    return (Result);
}
inline const debug_string GetFileName(debug_element *Element) {
    debug_string Result = {Element->FileNameCount, Element->GUID};
    return Result;
}

struct debug_parsed_name {
    uint32 HashValue = 0;
    uint32 FileNameCount = 0;
    uint32 NameStartsAt = 0;
    uint32 LineNumber = 0;
    uint32 NameLength = 0;
    const char *Name;
};
inline debug_parsed_name DebugParseName(const char *GUID) {
    debug_parsed_name Result = {};
    uint32 PipeCount = 0;
    const char *Scan = GUID;
    for (; *Scan; ++Scan) {
        if (*Scan == '|') {
            if (PipeCount == 0) {
                Result.FileNameCount = (uint32)(Scan - GUID);
                Result.LineNumber = atoi(Scan + 1);
            } else if (PipeCount == 1) {
            } else {
                Result.NameStartsAt = (uint32)(Scan - GUID + 1);
            }
            ++PipeCount;
        }
        Result.HashValue += 65599 * Result.HashValue + *Scan;
    }
    Result.NameLength = (uint32)((Scan - GUID) - Result.NameStartsAt);
    Result.Name = GUID + Result.NameStartsAt;
    return Result;
}

enum debug_element_add_op {
    DebugElement_AddToGroup = 0x1,
    DebugElement_CreateHierarchy = 0x2,
};

inline debug_element *GetElementFromEvent(debug_state *DebugState, uint32 HashValue,
                                          const char *GUID) {
    debug_element *Result = 0;
    uint32 Index = HashValue % ArrayCount(DebugState->ElementsHash);
    for (debug_element *Chain = DebugState->ElementsHash[Index]; Chain; Chain = Chain->NextInHash) {
        if (StringsAreEqual(Chain->GUID, GUID)) {
            Result = Chain;
            break;
        }
    }
    return Result;
}

internal debug_element *GetElementFromEvent(debug_state *DebugState, debug_event *Event,
                                            debug_variable_link *Parent = 0,
                                            uint32 Op = DebugElement_AddToGroup) {
    Assert(Event->GUID != 0);
    if (!Parent) {
        Parent = DebugState->RootGroup;
    }
    debug_parsed_name ParsedName = DebugParseName(Event->GUID);
    uint32 Index = ParsedName.HashValue % ArrayCount(DebugState->ElementsHash);
    debug_element *Result = GetElementFromEvent(DebugState, ParsedName.HashValue, Event->GUID);
    if (!Result) {
        Result = PushStruct(&DebugState->DebugArena, debug_element);
        Result->OriginalGUID = Event->GUID;
        Result->GUID = PushString(&DebugState->DebugArena, Event->GUID);
        Result->FileNameCount = ParsedName.FileNameCount;
        Result->LineNumber = ParsedName.LineNumber;
        Result->NameStartsAt = ParsedName.NameStartsAt;
        Result->NextInHash = DebugState->ElementsHash[Index];
        Result->Type = Event->Type;
        DebugState->ElementsHash[Index] = Result;
        debug_variable_link *ParentGroup = Parent;
        if (Op & DebugElement_CreateHierarchy) {
            ParentGroup = GetGroupForHierarchicalName(DebugState, Parent, GetName(Result));
        }
        if (Op & DebugElement_AddToGroup) {
            AddElementToGroup(DebugState, ParentGroup, Result);
        }
    }
    return Result;
}
inline void IncrementFrameOrdinal(uint32 *Ordinal) {
    *Ordinal = (*Ordinal + 1) % DEBUG_FRAME_COUNT;
}
internal void FreeOldestFrame(debug_state *DebugState) {
    FreeFrame(DebugState, DebugState->OldestFrameOrdinal);
    if (DebugState->OldestFrameOrdinal == DebugState->MostRecentFrameOrdinal) {
        IncrementFrameOrdinal(&DebugState->MostRecentFrameOrdinal);
    }
    IncrementFrameOrdinal(&DebugState->OldestFrameOrdinal);
}

inline debug_frame *GetCollationFrame(debug_state *DebugState) {
    debug_frame *Result = &DebugState->Frames[DebugState->CollatingFrameOrdinal];
    return Result;
}

internal debug_stored_event *StoreEvent(debug_state *DebugState, debug_element *Element,
                                        debug_event *Event) {
    debug_stored_event *Result = 0;

    while (!Result) {
        Result = DebugState->FirstFreeStoredEvent;
        if (Result) {
            DebugState->FirstFreeStoredEvent = Result->NextFree;
        } else {
            if (ArenaHasRoomFor(&DebugState->PerFrameArena, sizeof(debug_stored_event))) {
                Result = PushStruct(&DebugState->PerFrameArena, debug_stored_event);
            } else {
                FreeOldestFrame(DebugState);
            }
        }
    }
    debug_frame *CollationFrame = GetCollationFrame(DebugState);
    Result->Next = 0;
    Result->FrameIndex = CollationFrame->FrameIndex;
    Result->Event = *Event;
    ++CollationFrame->StoreEventCount;
    debug_element_frame *Frame = Element->Frames + DebugState->CollatingFrameOrdinal;

    if (Frame->MostRecentEvent) {
        Frame->MostRecentEvent = Frame->MostRecentEvent->Next = Result;
    } else {
        Frame->OldestEvent = Frame->MostRecentEvent = Result;
    }
    return Result;
}

internal void CollateDebugRecords(debug_state *DebugState, uint32 EventCount,
                                  debug_event *EventArray) {
    for (uint32 EventIndex = 0; EventIndex < EventCount; ++EventIndex) {
        debug_event *Event = EventArray + EventIndex;
        if (Event->Type == DebugType_FrameMarker) {
            debug_frame *CollationFrame = GetCollationFrame(DebugState);
            Assert(CollationFrame);
            CollationFrame->EndClock = Event->Clock;
            if (CollationFrame->RootProfileNode) {
                CollationFrame->RootProfileNode->ProfileNode.Duration =
                    (CollationFrame->EndClock - CollationFrame->BeginClock);
            }
            CollationFrame->WallSecondsElapsed = Event->Value_real32;
            ++DebugState->TotalFrameCount;
            if (DebugState->Paused) {
                FreeFrame(DebugState, DebugState->CollatingFrameOrdinal);
            } else {
                DebugState->MostRecentFrameOrdinal = DebugState->CollatingFrameOrdinal;
                IncrementFrameOrdinal(&DebugState->CollatingFrameOrdinal);
                if (DebugState->CollatingFrameOrdinal == DebugState->OldestFrameOrdinal) {
                    FreeOldestFrame(DebugState);
                }
                CollationFrame = GetCollationFrame(DebugState);
                InitFrame(DebugState, Event->Clock, CollationFrame);
            }

        } else {
            debug_frame *CollationFrame = GetCollationFrame(DebugState);
            Assert(CollationFrame);
            uint32 FrameIndex = DebugState->TotalFrameCount - 1;
            debug_thread *Thread = GetDebugThread(DebugState, Event->ThreadID);
            uint64 RelativeClock = Event->Clock - CollationFrame->BeginClock;

            debug_variable_link *DefaultParentGroup = DebugState->RootGroup;
            if (Thread->FirstOpenDataBlock) {
                DefaultParentGroup = Thread->FirstOpenDataBlock->Group;
            }

            (void)RelativeClock;
            switch (Event->Type) {
                case DebugType_BeginBlock: {
                    ++DebugState->BeginBlockCount;
                    ++CollationFrame->ProfileBlockCount;
                    debug_element *Element = GetElementFromEvent(
                        DebugState, Event, DebugState->ProfileGroup, DebugElement_AddToGroup);
                    debug_stored_event *ParentEvent = CollationFrame->RootProfileNode;

                    uint64 ClockBasis = CollationFrame->BeginClock;
                    if (Thread->FirstOpenCodeBlock) {
                        ParentEvent = Thread->FirstOpenCodeBlock->Node;
                        ClockBasis = Thread->FirstOpenCodeBlock->BeginClock;
                    } else if (!ParentEvent) {
                        debug_event NullEvent = {};
                        ParentEvent =
                            StoreEvent(DebugState, DebugState->RootProfileElement, &NullEvent);
                        debug_profile_node *Node = &ParentEvent->ProfileNode;
                        Node->Element = 0;
                        Node->FirstChild = 0;
                        Node->ParentRelativeClock = 0;
                        Node->NextSameParent = 0;
                        Node->Duration = 0;
                        Node->ThreadOrdinal = 0;
                        Node->CoreIndex = 0;
                        Node->DurationOfChildren = 0;
                        ClockBasis = CollationFrame->BeginClock;
                        CollationFrame->RootProfileNode = ParentEvent;
                    }
                    debug_stored_event *StoredEvent = StoreEvent(DebugState, Element, Event);
                    debug_profile_node *Node = &StoredEvent->ProfileNode;

                    Node->Element = Element;
                    Node->FirstChild = 0;
                    Node->ParentRelativeClock = (uint32)(Event->Clock - ClockBasis);
                    Node->Duration = 0;
                    Node->DurationOfChildren = 0;
                    Node->ThreadOrdinal = (uint16)Thread->LaneIndex;
                    Node->CoreIndex = Event->CoreIndex;

                    Node->NextSameParent = ParentEvent->ProfileNode.FirstChild;
                    ParentEvent->ProfileNode.FirstChild = StoredEvent;
                    open_debug_block *DebugBlock = AllocateOpenDebugBlock(
                        DebugState, Element, FrameIndex, Event, &Thread->FirstOpenCodeBlock);
                    DebugBlock->Node = StoredEvent;
                } break;
                case DebugType_EndBlock: {
                    ++DebugState->EndBlockCount;
                    if (Thread->FirstOpenCodeBlock) {
                        open_debug_block *MatchingBlock = Thread->FirstOpenCodeBlock;
                        Assert(Thread->ID == Event->ThreadID);
                        debug_profile_node *Node = &MatchingBlock->Node->ProfileNode;
                        Node->Duration = (Event->Clock - MatchingBlock->BeginClock);
                        DeallocateOpenDebugBlock(DebugState, &Thread->FirstOpenCodeBlock);
                        if (Thread->FirstOpenCodeBlock) {
                            debug_profile_node *ParentNode =
                                &Thread->FirstOpenCodeBlock->Node->ProfileNode;
                            ParentNode->DurationOfChildren += Node->Duration;
                        }
                    }
                } break;
                case DebugType_OpenDataBlock: {
                    ++CollationFrame->DataBlockCount;
                    open_debug_block *DebugBlock = AllocateOpenDebugBlock(
                        DebugState, 0, FrameIndex, Event, &Thread->FirstOpenDataBlock);
                    debug_parsed_name ParsedName = DebugParseName(Event->GUID);
                    DebugBlock->Group = GetGroupForHierarchicalName(DebugState, DefaultParentGroup,
                                                                    ParsedName.Name, true);
                } break;
                case DebugType_CloseDataBlock: {
                    if (Thread->FirstOpenDataBlock) {
                        open_debug_block *MatchingBlock = Thread->FirstOpenDataBlock;
                        (void)MatchingBlock;
                        Assert(Thread->ID == Event->ThreadID);
                        DeallocateOpenDebugBlock(DebugState, &Thread->FirstOpenDataBlock);
                    }
                } break;

                default: {
                    debug_element *Element =
                        GetElementFromEvent(DebugState, Event, DefaultParentGroup,
                                            DebugElement_AddToGroup | DebugElement_CreateHierarchy);
                    Element->OriginalGUID = Event->GUID;
                    StoreEvent(DebugState, Element, Event);
                } break;
            }
        }
    }

#if 0
    uint32 TotalRecordCount = 0;
    debug_counter_state *CounterArray[MAX_DEBUG_TRANSLATION_UNITS];
    debug_counter_state *CurrentCounter = DebugState->CounterStates;

    for (uint32 UnitIndex = 0; UnitIndex < MAX_DEBUG_TRANSLATION_UNITS; ++UnitIndex) {
        CounterArray[UnitIndex] = CurrentCounter;
        TotalRecordCount += GlobalDebugTable->RecordCount[UnitIndex];
        CurrentCounter += GlobalDebugTable->RecordCount[UnitIndex];
    }
    DebugState->CounterCount = TotalRecordCount;

    for (uint32 CounterIndex = 0; CounterIndex < DebugState->CounterCount; ++CounterIndex) {
        debug_counter_state *Dest = DebugState->CounterStates + CounterIndex;
        Dest->Snapshots[DebugState->SnapshotIndex].HitCount = 0;
        Dest->Snapshots[DebugState->SnapshotIndex].CycleCount = 0;
    }

    for (uint32 EventIndex = 0; EventIndex < EventCount; ++EventIndex) {
        debug_event *Event = Events + EventIndex;
        debug_counter_state *Dest = CounterArray[Event->TranslationUnit] + Event->DebugRecordIndex;

        debug_record *Source =
            GlobalDebugTable->Records[Event->TranslationUnit] + Event->DebugRecordIndex;
        Dest->FileName = Source->FileName;
        Dest->BlockName = Source->BlockName;
        Dest->LineNumber = Source->LineNumber;
        if (Event->Type == DebugType_BeginBlock) {
            ++Dest->Snapshots[DebugState->SnapshotIndex].HitCount;
            Dest->Snapshots[DebugState->SnapshotIndex].CycleCount -= Event->Clock;

        } else if (Event->Type == DebugType_EndBlock) {
            Dest->Snapshots[DebugState->SnapshotIndex].CycleCount += Event->Clock;
        }
    }
#endif
}
void RestartCollation(debug_state *DebugState, uint32 InvalidEventArrayIndex);

inline debug_state *DebugGetState(game_memory *Memory) {
    debug_state *DebugState = 0;
    if (Memory) {
        DebugState = (debug_state *)Memory->DebugStorage;
        if (!DebugState->IsInitialized) {
            DebugState = 0;
        }
    }
    return DebugState;
}
#if GAME_INTERNAL
inline debug_state *DebugGetState(void) {  //
    debug_state *Result = DebugGetState(DebugGlobalMemory);
    return Result;
}

internal debug_tree *AddTree(debug_state *DebugState, debug_variable_link *Group, v2 AtP) {
    debug_tree *Tree = PushStruct(&DebugState->DebugArena, debug_tree);
    Tree->UIP = AtP;
    Tree->Group = Group;
    DLIST_INSERT(&DebugState->TreeSentinel, Tree);
    return Tree;
}

internal void DEBUGStart(debug_state *DebugState, game_render_commands *Commands,
                         game_assets *Assets, uint32 MainGenerationID, real32 Width,
                         real32 Height) {  //
    TIMED_FUNCTION();
    if (DebugState) {
        if (!DebugState->IsInitialized) {
            DebugState->FrameBarLaneCount = 0;
            DebugState->FirstThread = 0;
            DebugState->FirstFreeThread = 0;
            DebugState->FirstFreeBlock = 0;

            DebugState->TotalFrameCount = 0;
            DebugState->MostRecentFrameOrdinal = 0;
            DebugState->CollatingFrameOrdinal = 1;
            DebugState->OldestFrameOrdinal = 0;
            DebugState->FrameCount = 0;

            DebugState->HighPriorityQueue = DebugGlobalMemory->HighPriorityQueue;
            DebugState->TreeSentinel.Next = &DebugState->TreeSentinel;
            DebugState->TreeSentinel.Prev = &DebugState->TreeSentinel;
            DebugState->TreeSentinel.Group = 0;
            memory_index TotalMemorySize =
                DebugGlobalMemory->DebugStorageSize - sizeof(debug_state);
            InitializeArena(&DebugState->DebugArena, TotalMemorySize, DebugState + 1);
#if 1
            SubArena(&DebugState->PerFrameArena, &DebugState->DebugArena, (TotalMemorySize / 2));
#else
            SubArena(&DebugState->PerFrameArena, &DebugState->DebugArena, 8 * 1024 * 1024);
#endif

            DebugState->RootGroup = CreateVariableLink(DebugState, 4, "root");
            DebugState->RootInfoSize = 256;
            DebugState->RootInfo = DebugState->RootGroup->Name =
                (char *)PushSize(&DebugState->DebugArena, DebugState->RootInfoSize);

            DebugState->ProfileGroup = CreateVariableLink(DebugState, 7, "Profile");

#if 0
            debug_variable_definition_context Context = {};

            Context.State = DebugState;
            Context.Arena = &DebugState->DebugArena;
            Context.GroupDepth = 0;
            Context.GroupStack[0] = 0;

            DebugState->RootGroup = DEBUGBeginVariableGroup(&Context, "ROOT");
            DEBUGBeginVariableGroup(&Context, "Debuging");
            DEBUGCreateVariables(&Context);
            DEBUGBeginVariableGroup(&Context, "Profile");
            DEBUGBeginVariableGroup(&Context, "By Thread");
            DEBUGAddVariable(&Context, DebugType_CounterThreadList, "");
            DEBUGEndVariableGroup(&Context);
            DEBUGBeginVariableGroup(&Context, "By Function");
            DEBUGAddVariable(&Context, DebugType_CounterThreadList, "");

            DEBUGEndVariableGroup(&Context);
            DEBUGEndVariableGroup(&Context);

            DEBUGBeginVariableGroup(&Context, "Test Bitmap");
            asset_vector MatchVector = {};
            MatchVector.E[Tag_FacingDirection] = 0.0f;
            asset_vector WeightVector = {};
            WeightVector.E[Tag_FacingDirection] = 1.0f;
            bitmap_id ID = GetBestMatchBitmapFrom(Assets, Asset_Head, &MatchVector, &WeightVector);
            DEBUGAddVariable(&Context, "Test Bitmap", ID);
            DEBUGEndVariableGroup(&Context);
            DEBUGEndVariableGroup(&Context);

            DEBUGEndVariableGroup(&Context);
            Assert(Context.GroupDepth == 0);
#endif
            debug_event RootProfileEvent{};
            RootProfileEvent.GUID = DEBUG_NAME("RootProfile");
            DebugState->RootProfileElement =
                GetElementFromEvent(DebugState, &RootProfileEvent, 0, DebugElement_AddToGroup);
            DebugState->Paused = false;
            DebugState->IsInitialized = true;
            AddTree(DebugState, DebugState->RootGroup, v2{-0.5f * Width, 0.5f * Height});
        }
        DebugState->RenderGroup = BeginRenderGroup(Assets, Commands, MainGenerationID, false,
                                                   Commands->Width, Commands->Height);

        DebugState->DebugFont = PushFont(&DebugState->RenderGroup, DebugState->FontID);
        DebugState->DebugFontInfo = GetFontInfo(DebugState->RenderGroup.Assets, DebugState->FontID);

        asset_vector MatchVector = {};
        asset_vector WeightVector = {};
        DebugState->Width = Width;
        DebugState->Height = Height;
        MatchVector.E[Tag_FontType] = (real32)FontType_Debug;
        WeightVector.E[Tag_FontType] = 1.0f;
        DebugState->FontID = GetBestMatchFontFrom(Assets, Asset_Font, &MatchVector, &WeightVector);
        DebugState->FontScale = 0.4f;
        Orthographic(&DebugState->RenderGroup, (int32)Width, (int32)Height, 1);
        DebugState->LeftEdge = -0.5f * Width;
        DebugState->RightEdge = 0.5f * Width;

        DebugState->TextTransform = DefaultFlatTransform();
        DebugState->ShadowTransform = DefaultFlatTransform();
        DebugState->BackingTransform = DefaultFlatTransform();
        DebugState->UITransform = DefaultFlatTransform();

        DebugState->ShadowTransform.ChunkZ = 100000;
        DebugState->TextTransform.ChunkZ = 400000;
        DebugState->BackingTransform.ChunkZ = 100000;
        DebugState->UITransform.ChunkZ = 300000;
        DebugState->ToolTipTransform.ChunkZ = 500000;
        DebugState->DefaultClipRect = DebugState->RenderGroup.CurrentClipRectIndex;
        DebugState->RenderTarget = 0;
        DebugState->ToolTipCount = 0;
        if (!DebugState->Paused) {
            DebugState->ViewingFrameOrdinal = DebugState->MostRecentFrameOrdinal;
        }
    }
}

inline bool32 IsHex(char Char) {
    bool32 Result = (((Char >= '0') && (Char <= '9')) ||  //
                     ((Char >= 'A') && (Char <= 'F')));
    return Result;
}

inline uint32 GetHex(char Char) {
    uint32 Result = 0;
    if ((Char >= '0') && (Char <= '9')) {
        Result = Char - '0';
    }
    if ((Char >= 'A') && (Char <= 'F')) {
        Result = Char - 'A' + 0xA;
    }
    return Result;
}

internal rectangle2 DebugTextOp(debug_text_op Op, debug_state *DebugState, v2 P, const char *String,
                                v4 Color = {1, 1, 1, 1}, real32 AtZ = 0.0f) {
    rectangle2 Result = InvertedInfinityRectangle2();
    if (DebugState && DebugState->DebugFont) {
        render_group *RenderGroup = &DebugState->RenderGroup;
        loaded_font *Font = DebugState->DebugFont;
        hha_font *FontInfo = DebugState->DebugFontInfo;
        real32 AtX = P.x;
        real32 AtY = P.y;
        real32 CharScale = DebugState->FontScale;
        uint32 PrevCodePoint = 0;
        for (const char *At = String; *At;) {
            if ((At[0] == '\\') &&  //
                (At[1] == '#') &&   //
                (At[2] != 0) &&     //
                (At[3] != 0) &&     //
                (At[4] != 0)) {
                real32 CScale = 1.0f / 9.0f;
                Color = v4{Clamp01(CScale * (At[2] - '0')),  //
                           Clamp01(CScale * (At[3] - '0')),  //
                           Clamp01(CScale * (At[4] - '0')),  //
                           1.0f};
                At += 5;
            } else if ((At[0] == '\\') &&  //
                       (At[1] == '^') &&   //
                       (At[2] != 0)) {
                real32 CScale = 1.0f / 9.0f;
                CharScale = DebugState->FontScale * Clamp01(CScale * (At[2] - '0'));
                At += 3;
            } else {
                uint32 CodePoint = *At;

                if ((At[0] == '\\') &&                     //
                    (At[1] == '0') &&                      //
                    ((At[2] == 'x') || (At[2] == 'X')) &&  //
                    (IsHex(At[3])) &&                      //
                    (IsHex(At[4])) &&                      //
                    (IsHex(At[5])) &&                      //
                    (IsHex(At[6]))) {
                    CodePoint = ((GetHex(At[3]) << 12) |  //
                                 (GetHex(At[4]) << 8) |   //
                                 (GetHex(At[5]) << 4) |   //
                                 (GetHex(At[6]) << 0));
                    At += 6;
                }

                real32 AdvanceX = CharScale * GetHorizontalAdvanceForPair(FontInfo, Font,
                                                                          PrevCodePoint, CodePoint);
                AtX += AdvanceX;
                if (*At != ' ') {
                    bitmap_id BitmapID =
                        GetBitmapForGlyph(RenderGroup->Assets, FontInfo, Font, CodePoint);
                    hha_bitmap *BitmapInfo = GetBitmapInfo(RenderGroup->Assets, BitmapID);
                    real32 BitmapScale = CharScale * (real32)BitmapInfo->Dim[1];
                    v3 BitmapOffset = v3{AtX, AtY, AtZ};
                    if (Op == DEBUGTextOp_DrawText) {
                        PushBitmap(RenderGroup, &DebugState->ShadowTransform, BitmapID, BitmapScale,
                                   BitmapOffset + v3{2.0f, -2.0f, 0.0f}, v4{0, 0, 0, 1.0f}, 1.0f);
                        PushBitmap(RenderGroup, &DebugState->TextTransform, BitmapID, BitmapScale,
                                   BitmapOffset, Color, 1.0f);

                    } else {
                        Assert(Op == DEBUGTextOp_SizeText);
                        loaded_bitmap *Bitmap =
                            GetBitmap(RenderGroup->Assets, BitmapID, RenderGroup->GenerationID);
                        if (Bitmap) {
                            object_transform Flat = DefaultFlatTransform();
                            used_bitmap_dim Dim = GetUsedBitmapDim(RenderGroup, &Flat, Bitmap,
                                                                   BitmapScale, BitmapOffset, 1.0f);
                            rectangle2 GlyphDim = RectMinDim(Dim.P.xy, Dim.Size);
                            Result = Union(Result, GlyphDim);
                        }
                    }
                }
                ++At;
                PrevCodePoint = CodePoint;
            }
        }
    }
    return Result;
}

rectangle2 TextOutAt(v2 P, const char *String, v4 Color, real32 AtZ) {
    rectangle2 Result = {};
    debug_state *DebugState = DebugGetState();
    if (DebugState) {
        render_group *RenderGroup = &DebugState->RenderGroup;
        loaded_font *Font = PushFont(RenderGroup, DebugState->FontID);
        if (Font) {
            Result = DebugTextOp(DEBUGTextOp_DrawText, DebugState, P, String, Color, AtZ);
        }
    }
    return Result;
}

rectangle2 DEBUGGetTextSize(debug_state *DebugState, const char *String) {
    rectangle2 Result = DebugTextOp(DEBUGTextOp_SizeText, DebugState, v2{0, 0}, String);
    return Result;
}

rectangle2 DEBUGGetTextSize(debug_state *DebugState, v2 At, const char *String) {
    rectangle2 Result = DebugTextOp(DEBUGTextOp_SizeText, DebugState, At, String);
    return Result;
}

void DebugTextLine(const char *String) {
    debug_state *DebugState = DebugGetState();
    if (DebugState) {
        if (DebugState->DebugFont) {
            TextOutAt(v2{DebugState->LeftEdge,
                         DebugState->FontScale * GetStartingBaselineY(DebugState->DebugFontInfo)},
                      String);
        }
    }
}

internal void OutputDebugRecords(uint32 CounterCount, debug_record *Counters) {
    (void)CounterCount;
    (void)Counters;
#if 0
    for (uint32 CounterIndex = 0; CounterIndex < CounterCount; ++CounterIndex) {
        debug_record *Counter = Counters + CounterIndex;
        uint64 HitCount_CycleCount = AtomicExchangeUInt64(&Counter->HitCount_CycleCount, 0);
        uint32 HitCount = (uint32)(HitCount_CycleCount >> 32);
        uint32 CycleCount = (uint32)(HitCount_CycleCount & 0xFFFF'FFFF);
        Counter->HitCount_CycleCount = 0;
        if (HitCount) {
#if 1
            char TextBuffer[256];
            FormatString(sizeof(TextBuffer),TextBuffer,  "%32s(%4d): %10ucy %10uh %10ucy/h\n",  //
                        Counter->BlockName,                                                    //
                        Counter->LineNumber,                                                   //
                        CycleCount,                                                            //
                        HitCount,                                                              //
                        CycleCount / HitCount);
            DebugTextLine(TextBuffer);
            HitCount = 0;
            CycleCount = 0;
        }
    }
#endif
#endif
}

// 【statistic】 n. 统计量 a. 统计的, 统计学的
struct debug_statistic {
    real64 Min;
    real64 Max;
    real64 Sum;
    real64 Avg;
    int32 Count;
};

inline void BeginDebugStatistic(debug_statistic *Stat) {
    Stat->Count = 0;
    Stat->Min = Real32Maximum;
    Stat->Max = -Real32Maximum;
    Stat->Sum = 0.0f;
    Stat->Avg = 0.0f;
}

inline void EndDebugStatistic(debug_statistic *Stat) {
    if (Stat->Count != 0) {
        Stat->Avg = Stat->Sum / (real64)Stat->Count;
    } else {
        Stat->Max = 0.0f;
        Stat->Min = 0.0f;
    }
}

inline void AccumulateStatistic(debug_statistic *Stat, real64 Value) {  //
    ++Stat->Count;
    if (Stat->Min > Value) {
        Stat->Min = Value;
    }
    if (Stat->Max < Value) {
        Stat->Max = Value;
    }
    Stat->Sum += Value;
}

internal int64 DEBUGEventToText(char *Buffer, char *End, debug_element *Element, debug_event *Event,
                                uint32 Flags) {
    (void)Flags;
    char *At = Buffer;
    const char *Name = 0;
    if (Element->GUID) {
        if (Global_Debug_FullGUIDName) {
            Name = Element->GUID;
        } else {
            Name = DebugParseName(Element->GUID).Name;
        }
    }
    v4 ItemColor = {1, 1, 1, 1};
    (void)ItemColor;
    if (Flags & DEBUGVarToText_AddDebugUI) {
        At += FormatString((End - At), At, "#define DEBUGUI_");
    }
    if (Flags & DEBUGVarToText_AddName) {
        const char *UseName = Name;
        if (Flags & DEBUGVarToText_StartAtLastSlash) {
            for (const char *Scan = Name; *Scan; ++Scan) {
                if ((Scan[0] == '/') && (Scan[1] != 0)) {
                    UseName = Scan + 1;
                }
            }
        }
        At += FormatString((End - At), At, "%s%s ", UseName,
                           (Flags & DEBUGVarToText_Colon) ? ":" : "");
    }
    if (Flags & DEBUGVarToText_AddValue) {
        switch (Event->Type) {
            case DebugType_Unknown: {
            } break;
            case DebugType_bool32: {
                if (Flags & DEBUGVarToText_PrettyBools) {
                    At += FormatString((End - At), At, "%s", Event->Value_int32 ? "TRUE" : "FALSE");
                } else {
                    At += FormatString((End - At), At, "%d", Event->Value_int32);
                }
            } break;
            case DebugType_int32: {
                At += FormatString((End - At), At, "%d", Event->Value_int32);
            } break;
            case DebugType_uint32: {
                At += FormatString((End - At), At, "%d", Event->Value_uint32);
            } break;
            case DebugType_real32: {
                At += FormatString((End - At), At, "%f", Event->Value_real32);
                if (Flags & DEBUGVarToText_FloatSuffix) {
                    *At++ = 'f';
                }
            } break;
            case DebugType_v2: {
                At +=
                    FormatString((End - At), At, "v2{%f,%f}", Event->Value_v2.x, Event->Value_v2.y);
            } break;
            case DebugType_v3: {
                At += FormatString((End - At), At, "v3{%f,%f,%f}", Event->Value_v3.x,
                                   Event->Value_v3.y, Event->Value_v3.z);
            } break;
            case DebugType_v4: {
                At += FormatString((End - At), At, "v4{%f,%f,%f,%f}", Event->Value_v4.x,
                                   Event->Value_v4.y, Event->Value_v4.z, Event->Value_v4.w);
            } break;
            case DebugType_rectangle2: {
                At += FormatString((End - At), At,
                                   "Rect2{%f,%f->%f,%f}",          //
                                   Event->Value_rectangle2.Min.x,  //
                                   Event->Value_rectangle2.Min.y,  //
                                   Event->Value_rectangle2.Max.x,  //
                                   Event->Value_rectangle2.Max.y);
            } break;
            case DebugType_rectangle3: {
                At += FormatString((End - At), At,
                                   "Rect2{%f,%f,%f->%f,%f,%f}",    //
                                   Event->Value_rectangle3.Min.x,  //
                                   Event->Value_rectangle3.Min.y,  //
                                   Event->Value_rectangle3.Min.z,  //
                                   Event->Value_rectangle3.Max.x,  //
                                   Event->Value_rectangle3.Max.y,  //
                                   Event->Value_rectangle3.Max.z);
            } break;
            case DebugType_bitmap_id: {
            } break;
            case DebugType_OpenDataBlock: {
                At += FormatString((End - At), At, "%s", Event->GUID);
            } break;
            case DebugType_BitmapDisplay: {
            } break;
            case DebugType_memory_arena_p: {
            } break;
            default: {
                At += FormatString((End - At), At,
                                   "UNHANDLED: %s",  //
                                   Event->GUID);
            } break;
        }
    }
    if (Flags & DEBUGVarToText_LineFeedEnd) {
        *At++ = '\n';
    }
    if (Flags & DEBUGVarToText_NullTerminator) {
        *At++ = 0;
    }
    return (At - Buffer);
}

bool32 DebugIDsAreEqual(debug_id A, debug_id B);
bool32 InteractionsAreEqual(debug_interaction A, debug_interaction B) {
    bool32 Result = (DebugIDsAreEqual(A.ID, B.ID) && (A.Type == B.Type) &&
                     (A.Generic == B.Generic) && (A.Target == B.Target));
    return Result;
}

bool32 InteractionsIsHot(debug_state *DebugState, debug_interaction B) {
    bool32 Result = InteractionsAreEqual(DebugState->HotInteraction, B);
    return Result;
}

inline bool32 DebugIDsAreEqual(debug_id A, debug_id B) {
    bool32 Result = ((A.Value[0] == B.Value[0]) && (A.Value[1] == B.Value[1]));
    return Result;
}

internal debug_view *GetOrCreateDebugViewFor(debug_state *DebugState, debug_id ID) {
    uint32 HashIndex = (((uint64)ID.Value[0] >> 2) + ((uint64)ID.Value[1] >> 2)) %
                       ArrayCount(DebugState->ViewHash);
    debug_view **HashSlot = DebugState->ViewHash + HashIndex;
    debug_view *Result = 0;

    for (debug_view *Search = *HashSlot; Search; Search = Search->NextInHash) {
        if (DebugIDsAreEqual(Search->ID, ID)) {
            Result = Search;
            break;
        }
    }
    if (!Result) {
        Result = PushStruct(&DebugState->DebugArena, debug_view);
        Result->ID = ID;
        Result->Type = DebugViewType_Unknow;
        Result->NextInHash = *HashSlot;
        *HashSlot = Result;
    }
    return Result;
}

inline debug_id DebugIDFromLink(debug_tree *Tree, debug_variable_link *Link) {
    debug_id Result = {};
    Result.Value[0] = Tree;
    Result.Value[1] = Link;
    return Result;
}

inline debug_id DebugIDFromGUID(debug_tree *Tree, const char *GUID) {
    debug_id Result = {};
    Result.Value[0] = Tree;
    Result.Value[1] = (void *)GUID;
    return Result;
}

inline debug_interaction ElementInteraction(debug_state *DebugState, debug_id DebugID,
                                            debug_interaction_type Type, debug_element *Element) {
    (void)DebugState;
    debug_interaction ItemInteraction = {};
    ItemInteraction.ID = DebugID;
    ItemInteraction.Type = Type;
    ItemInteraction.Element = Element;
    return ItemInteraction;
}

inline debug_interaction DebugLinkInteraction(debug_interaction_type Type,
                                              debug_variable_link *Link) {
    debug_interaction ItemInteraction = {};
    ItemInteraction.Link = Link;
    ItemInteraction.Type = Type;
    return ItemInteraction;
}

inline debug_interaction DebugIDInteraction(debug_interaction_type Type, debug_id ID) {
    debug_interaction ItemInteraction = {};
    ItemInteraction.ID = ID;
    ItemInteraction.Type = Type;
    return ItemInteraction;
}

internal bool32 IsSelected(debug_state *DebugState, debug_id ID) {
    bool32 Result = false;
    for (uint32 Index = 0; Index < DebugState->SelectedIDCount; ++Index) {
        if (DebugIDsAreEqual(ID, DebugState->SelectedID[Index])) {
            Result = true;
            break;
        }
    }
    return Result;
}

internal void ClearSelection(debug_state *DebugState) { DebugState->SelectedIDCount = 0; }

internal void AddToSelection(debug_state *DebugState, debug_id ID) {
    if ((DebugState->SelectedIDCount < ArrayCount(DebugState->SelectedID)) &&
        !IsSelected(DebugState, ID)) {
        DebugState->SelectedID[DebugState->SelectedIDCount++] = ID;
    }
}

void DEBUG_HIT(debug_id ID, real32 ZValue) {
    //
    (void)ID;
    (void)ZValue;
    debug_state *DebugState = DebugGetState();
    if (DebugState) {
        DebugState->NextHotInteraction = DebugIDInteraction(DebugInteraction_Select, ID);
    }
}
bool32 DEBUG_HIGHLIGHTED(debug_id ID, v4 *Color) {
    (void)Color;
    bool32 Result = false;
    debug_state *DebugState = DebugGetState();
    if (DebugState) {
        if (IsSelected(DebugState, ID)) {
            *Color = v4{0, 1, 1, 1};
            Result = true;
        }
        if (DebugIDsAreEqual(ID, DebugState->HotInteraction.ID)) {
            *Color = v4{1, 1, 0, 1};
            Result = true;
        }
    }
    return Result;
}
bool32 DEBUG_REQUESTED(debug_id ID) {
    bool32 Result = false;
    debug_state *DebugState = DebugGetState();
    if (DebugState) {
        Result = IsSelected(DebugState, ID) || DebugIDsAreEqual(ID, DebugState->HotInteraction.ID);
    }

    return Result;
}

inline debug_element *GetElementFromGUID(debug_state *DebugState, uint32 Index, const char *GUID) {
    debug_element *Result = 0;
    for (debug_element *Chain = DebugState->ElementsHash[Index]; Chain; Chain = Chain->NextInHash) {
        if (StringsAreEqual(Chain->GUID, GUID)) {
            Result = Chain;
            break;
        }
    }
    return Result;
}

inline debug_element *GetElementFromGUID(debug_state *DebugState, const char *GUID) {
    debug_element *Result = 0;
    if (GUID) {
        debug_parsed_name ParsedName = DebugParseName(GUID);
        uint32 Index = (ParsedName.HashValue % ArrayCount(DebugState->ElementsHash));
        Result = GetElementFromGUID(DebugState, Index, GUID);
    }
    return Result;
}

void DrawFrameSlider(debug_state *DebugState, debug_id SliderID, rectangle2 TotalRect, v2 MouseP,
                     debug_element *RootElement) {
    // (void)DebugState;
    // (void)DebugID;
    // (void)Bounds;
    // (void)MouseP;
    // (void)Element;
    uint32 FrameCount = ArrayCount(RootElement->Frames);
    if (FrameCount > 0) {
        PushRect(&DebugState->RenderGroup, &DebugState->BackingTransform, TotalRect, 0.0f,
                 v4{0, 0, 0, 0.25f});
        real32 BarWidth = (GetDim(TotalRect).x / (real32)FrameCount);
        real32 AtX = TotalRect.Min.x;
        real32 ThisMinY = TotalRect.Min.y;
        real32 ThisMaxY = TotalRect.Max.y;
        for (uint32 FrameIndex = 0; FrameIndex < FrameCount; ++FrameIndex) {
            rectangle2 RegionRect = RectMinMax(v2{AtX, ThisMinY}, v2{AtX + BarWidth, ThisMaxY});
            v4 HitColor = {1, 1, 1, 1};
            bool32 Highlight = false;
            if (FrameIndex == DebugState->ViewingFrameOrdinal) {
                HitColor = V4(1, 0, 0, 1);
                Highlight = true;
            }
            if (FrameIndex == DebugState->MostRecentFrameOrdinal) {
                HitColor = V4(0, 1, 0, 1);
                Highlight = true;
            }
            if (FrameIndex == DebugState->CollatingFrameOrdinal) {
                HitColor = V4(0, 0, 1, 1);
                Highlight = true;
            }
            if (FrameIndex == DebugState->OldestFrameOrdinal) {
                HitColor = V4(0, 0, 0, 1);
                Highlight = true;
            }
            if (Highlight) {
                PushRect(&DebugState->RenderGroup, &DebugState->UITransform, RegionRect, 0.0f,
                         HitColor);
            }

            PushRectOutline(&DebugState->RenderGroup, &DebugState->UITransform, RegionRect, 0.0f,
                            v4{0.5f, 0.5f, 0.5f}, 2);

            if (IsInRectangle(RegionRect, MouseP)) {
                tooltip_buffer TextBuffer = AddTooltip(DebugState);
                FormatString(TextBuffer.Size, TextBuffer.Data, "%10u", FrameIndex);
                DebugState->NextHotInteraction =
                    SetUInt32Interaction(SliderID, &DebugState->ViewingFrameOrdinal, FrameIndex);
            }
            AtX += BarWidth;
        }
    }
}

inline debug_interaction SetElementTypeInteration(debug_id DebugID, debug_element *Element,
                                                  debug_type Type) {
    debug_interaction Result = {};
    Result.ID = DebugID;
    Result.Type = DebugInteraction_SetElementType;
    Result.Element = Element;
    Result.UInt32 = Type;
    return Result;
}

inline debug_interaction SetUInt32Interaction(debug_id DebugID, uint32 *Target, uint32 Value) {
    debug_interaction Result = {};
    Result.ID = DebugID;
    Result.Type = DebugInteraction_SetUInt32;
    Result.Target = Target;
    Result.UInt32 = Value;
    return Result;
}

inline debug_interaction SetPointerInteraction(debug_id DebugID, void **Target, void *Value) {
    debug_interaction Result = {};
    Result.ID = DebugID;
    Result.Type = DebugInteraction_SetPointer;
    Result.Target = Target;
    Result.Pointer = Value;
    return Result;
}

void DrawArenaOccupancy(debug_state *DebugState, debug_id DebugID, rectangle2 FrameRect, v2 MouseP,
                        debug_element *RootElement) {
    (void)MouseP;
    (void)DebugID;
    debug_element_frame *RootFrame = RootElement->Frames + DebugState->ViewingFrameOrdinal;
    debug_stored_event *Event = RootFrame->OldestEvent;
    if (Event) {
        memory_arena *Arena = Event->Event.Value_memory_arena_p;
        real32 t = (real32)(((real64)Arena->Used) / ((real64)Arena->Size));
        real32 SplitPoint = Lerp(FrameRect.Min.x, t, FrameRect.Max.x);
        rectangle2 UsedRect = RectMinMax(v2{FrameRect.Min.x, FrameRect.Min.y},  //
                                         v2{SplitPoint, FrameRect.Max.y});
        rectangle2 UnUsedRect = RectMinMax(v2{SplitPoint, FrameRect.Min.y},  //
                                           v2{FrameRect.Max.x, FrameRect.Max.y});
        PushRect(&DebugState->RenderGroup,  //
                 &DebugState->UITransform,  //
                 UsedRect, 0,               //
                 v4{1, 0.5f, 0, 1});
        PushRectOutline(&DebugState->RenderGroup,  //
                        &DebugState->UITransform,  //
                        UsedRect, 1,               //
                        v4{0, 0, 0, 1}, 2.0f);
        PushRect(&DebugState->RenderGroup,  //
                 &DebugState->UITransform,  //
                 UnUsedRect, 0,             //
                 v4{0, 1, 0, 1});
        PushRectOutline(&DebugState->RenderGroup,  //
                        &DebugState->UITransform,  //
                        UnUsedRect, 1,             //
                        v4{0, 0, 0, 1}, 2.0f);
    }
}

internal void DEBUGDrawElement(layout *Layout, debug_tree *Tree, debug_element *InElement,
                               debug_id DebugID, uint32 FrameOrdinal) {
    (void)Tree;
    (void)FrameOrdinal;
    debug_state *DebugState = Layout->DebugState;
    render_group *RenderGroup = &DebugState->RenderGroup;
    debug_interaction ItemInteraction =
        ElementInteraction(DebugState, DebugID, DebugInteraction_AutoModifyVariable, InElement);
    bool32 IsHot = InteractionsIsHot(DebugState, ItemInteraction);
    v4 ItemColor = IsHot ? v4{1, 1, 0, 1} : v4{1, 1, 1, 1};
    debug_view *View = GetOrCreateDebugViewFor(DebugState, DebugID);

    debug_stored_event *OldestStoredEvent =
        InElement->Frames[DebugState->ViewingFrameOrdinal].OldestEvent;

    switch (InElement->Type) {
        case DebugType_Unknown: {
        } break;
        case DebugType_bitmap_id: {
            debug_event *Event = OldestStoredEvent ? &OldestStoredEvent->Event : 0;
            layout_element LayoutElement = BeginElementRectangle(Layout, &View->InlineBlock.Dim);
            real32 BitmapScale = View->InlineBlock.Dim.y;
            loaded_bitmap *Bitmap = 0;
            if (Event) {
                Bitmap = GetBitmap(RenderGroup->Assets, Event->Value_bitmap_id,
                                   RenderGroup->GenerationID);
                if (Bitmap) {
                    used_bitmap_dim Dim =
                        GetUsedBitmapDim(RenderGroup, &DebugState->BackingTransform, Bitmap,
                                         BitmapScale, {0.0f, 0.0f, 0.0f}, 1.0f);
                    View->InlineBlock.Dim.x = Dim.Size.x;
                }
            }
            MakeElementSizable(&LayoutElement);

            DefaultInteraction(&LayoutElement, ItemInteraction);
            EndElement(&LayoutElement);

            PushRect(&DebugState->RenderGroup, &DebugState->BackingTransform, LayoutElement.Bounds,
                     0.0f, v4{0, 0, 0, 1.0f});
            if (Bitmap) {
                PushBitmap(&DebugState->RenderGroup, &DebugState->BackingTransform,
                           Event->Value_bitmap_id, BitmapScale,
                           ToV3(GetMinCorner(LayoutElement.Bounds), 1.0f), v4{1, 1, 1, 1}, 0.0f);
            }
        } break;
        case DebugType_memory_arena_p:
        case DebugType_ArenaOccupancy: {
            debug_view_arena_graph *Graph = &View->ArenaGraph;
            BeginRow(Layout);
            Label(Layout, GetName(InElement));
            BooleanButton(Layout, "Occupancy", (InElement->Type == DebugType_ArenaOccupancy),
                          SetUInt32Interaction(DebugID, (uint32 *)&InElement->Type,
                                               DebugType_ArenaOccupancy));
            EndRow(Layout);
            layout_element Element1 = BeginElementRectangle(Layout, &View->ProfileGraph.Block.Dim);
            if ((Graph->Block.Dim.x == 0) && (Graph->Block.Dim.y == 0)) {
                Graph->Block.Dim.x = 1500;
                Graph->Block.Dim.y = 480;
            }
            MakeElementSizable(&Element1);
            // DefaultInteraction(&Element, ItemInteraction);
            EndElement(&Element1);
            PushRect(&DebugState->RenderGroup, &DebugState->BackingTransform, Element1.Bounds, 0.0f,
                     v4{0, 0, 0, 0.8f});
            uint32 OldClipRect = RenderGroup->CurrentClipRectIndex;
            RenderGroup->CurrentClipRectIndex =
                PushClipRect(RenderGroup, &DebugState->BackingTransform, Element1.Bounds, 0.0f,
                             DebugState->RenderTarget);

#if 1
            debug_element *ViewingElement = GetElementFromGUID(DebugState, View->ProfileGraph.GUID);
            if (!ViewingElement) {
                ViewingElement = DebugState->RootProfileElement;
            }
#endif
            switch (InElement->Type) {
                case DebugType_ArenaOccupancy: {
                    DrawArenaOccupancy(DebugState, DebugID, Element1.Bounds, Layout->MouseP,
                                       InElement);

                } break;

                default: {
                } break;
            }
            RenderGroup->CurrentClipRectIndex = OldClipRect;
        } break;
        case DebugType_OpenDataBlock: {
        } break;
        case DebugType_CloseDataBlock: {
        } break;
        case DebugType_ThreadIntervalGraph:
        case DebugType_FrameBarGraph:
        case DebugType_TopClocksList: {
            BeginRow(Layout);
            debug_view_profile_graph *Graph = &View->ProfileGraph;

            ActionButton(Layout, "Root", SetPointerInteraction(DebugID, (void **)&Graph->GUID, 0));
            BooleanButton(Layout, "ThreadIntervalGraph",
                          (InElement->Type == DebugType_ThreadIntervalGraph),
                          SetUInt32Interaction(DebugID, (uint32 *)&InElement->Type,
                                               DebugType_ThreadIntervalGraph));
            BooleanButton(
                Layout, "FrameBarGraph", (InElement->Type == DebugType_FrameBarGraph),
                SetUInt32Interaction(DebugID, (uint32 *)&InElement->Type, DebugType_FrameBarGraph));
            BooleanButton(
                Layout, "Clocks", (InElement->Type == DebugType_TopClocksList),
                SetUInt32Interaction(DebugID, (uint32 *)&InElement->Type, DebugType_TopClocksList));

            EndRow(Layout);

            layout_element Element1 = BeginElementRectangle(Layout, &View->ProfileGraph.Block.Dim);
            if ((Graph->Block.Dim.x == 0) && (Graph->Block.Dim.y == 0)) {
                Graph->Block.Dim.x = 1500;
                Graph->Block.Dim.y = 480;
            }
            MakeElementSizable(&Element1);
            // DefaultInteraction(&Element, ItemInteraction);
            EndElement(&Element1);
            PushRect(&DebugState->RenderGroup, &DebugState->BackingTransform, Element1.Bounds, 0.0f,
                     v4{0, 0, 0, 0.8f});
            uint32 OldClipRect = RenderGroup->CurrentClipRectIndex;
            RenderGroup->CurrentClipRectIndex =
                PushClipRect(RenderGroup, &DebugState->BackingTransform, Element1.Bounds, 0.0f,
                             DebugState->RenderTarget);

#if 1
            debug_element *ViewingElement = GetElementFromGUID(DebugState, View->ProfileGraph.GUID);
            if (!ViewingElement) {
                ViewingElement = DebugState->RootProfileElement;
            }
#endif
            switch (InElement->Type) {
                case DebugType_ThreadIntervalGraph: {
                    DrawProfileIn(DebugState, DebugID, Element1.Bounds, Layout->MouseP,
                                  ViewingElement);
                } break;
                case DebugType_FrameBarGraph: {
                    DrawFrameBars(DebugState, DebugID, Element1.Bounds, Layout->MouseP,
                                  ViewingElement);
                } break;
                case DebugType_TopClocksList: {
                    DrawTopClocksList(DebugState, DebugID, Element1.Bounds, Layout->MouseP,
                                      ViewingElement);
                } break;
                default: {
                } break;
            }
            RenderGroup->CurrentClipRectIndex = OldClipRect;
        } break;
        case DebugType_LastFrameInfo: {
            debug_frame *MostRecentFrame = DebugState->Frames + DebugState->ViewingFrameOrdinal;
            char Text[256];
            FormatString(sizeof(Text), Text,  //
                         "Viewing frame time: %.02fms %de %dp %dd",
                         MostRecentFrame->WallSecondsElapsed * 1000.0f,
                         MostRecentFrame->StoreEventCount, MostRecentFrame->ProfileBlockCount,
                         MostRecentFrame->DataBlockCount);
            BasicTextElement(Layout, Text, ItemInteraction);
        } break;
        case DebugType_DebugMemoryInfo: {
            char Text[256];
            FormatString(
                sizeof(Text), Text,  //
                "per frame arena remaining: %ukb",
                (uint32)GetArenaSizeRemaining(&DebugState->PerFrameArena, AlignNoClear(1)) / 1024);
            BasicTextElement(Layout, Text, ItemInteraction);
        } break;
        case DebugType_FrameSlider: {
            v2 *Dim = &View->InlineBlock.Dim;
            if ((Dim->x == 0) && (Dim->y == 0)) {
                Dim->x = 1400;
                Dim->y = 32;
            }
            layout_element LayoutElement = BeginElementRectangle(Layout, Dim);
            MakeElementSizable(&LayoutElement);
            EndElement(&LayoutElement);
            BeginRow(Layout);
            BooleanButton(
                Layout, "Pause", DebugState->Paused,
                SetUInt32Interaction(DebugID, (uint32 *)&DebugState->Paused, !DebugState->Paused));
            EndRow(Layout);
            DrawFrameSlider(DebugState, DebugID, LayoutElement.Bounds, Layout->MouseP, InElement);

        } break;
        default: {
            char Text[256];
            debug_event NullEvent{};
            NullEvent.GUID = InElement->GUID;
            NullEvent.Type = InElement->Type;
            debug_event *Event = OldestStoredEvent ? &OldestStoredEvent->Event : &NullEvent;
            DEBUGEventToText(Text, Text + sizeof(Text), InElement, Event,
                             DEBUGVarToText_AddName |             //
                                 DEBUGVarToText_FloatSuffix |     //
                                 DEBUGVarToText_NullTerminator |  //
                                 DEBUGVarToText_Colon |           //
                                 DEBUGVarToText_PrettyBools |     //
                                 DEBUGVarToText_AddValue);
            BasicTextElement(Layout, Text, ItemInteraction, ItemColor);
        } break;
    }
}

void DrawTreeLink(debug_state *DebugState, layout *Layout, debug_tree *Tree,
                  debug_variable_link *Link) {
    uint32 FrameOrdinal = DebugState->ViewingFrameOrdinal;
    if (HasChildren(Link)) {
        debug_view *View = GetOrCreateDebugViewFor(DebugState, DebugIDFromLink(Tree, Link));
        debug_interaction ItemInteraction =
            DebugIDInteraction(DebugInteraction_ToggleExpansion, DebugIDFromLink(Tree, Link));
        if (DebugState->AltUI) {
            ItemInteraction = DebugLinkInteraction(DebugInteraction_TearValue, Link);
        }
        const char *Text = Link->Name;

        rectangle2 TextBounds = DEBUGGetTextSize(DebugState, Text);
        v2 Dim = {GetDim(TextBounds).x, Layout->LineAdvance};

        bool32 IsHot = InteractionsIsHot(DebugState, ItemInteraction);
        v4 ItemColor = IsHot ? v4{1, 1, 0, 1} : v4{1, 1, 1, 1};

        layout_element Element = BeginElementRectangle(Layout, &Dim);
        DefaultInteraction(&Element, ItemInteraction);
        EndElement(&Element);
        TextOutAt({v2{GetMinCorner(Element.Bounds).x,
                      GetMaxCorner(Element.Bounds).y -
                          DebugState->FontScale * GetStartingBaselineY(DebugState->DebugFontInfo)}},
                  Text, ItemColor);

        if (View->Collapsible.ExpandedAlways) {
            ++Layout->Depth;
            for (debug_variable_link *SubLink = Link->FirstChild;  //
                 SubLink != GetSentinel(Link);                     //
                 SubLink = SubLink->Next) {
                DrawTreeLink(DebugState, Layout, Tree, SubLink);
            }
            --Layout->Depth;
        }
    } else {
        debug_id DebugID = DebugIDFromLink(Tree, Link);
        DEBUGDrawElement(Layout, Tree, Link->Element, DebugID, FrameOrdinal);
    }
}

internal void DrawTrees(debug_state *DebugState, v2 MouseP) {
    (void)DebugState;
    (void)MouseP;
    for (debug_tree *Tree = DebugState->TreeSentinel.Next; Tree != &DebugState->TreeSentinel;  //
         Tree = Tree->Next) {
        layout Layout = BeginLayout(DebugState, MouseP, Tree->UIP);
        debug_variable_link *Group = Tree->Group;
        if (Group) {
            DrawTreeLink(DebugState, &Layout, Tree, Group);
        }

        debug_interaction MoveInteraction = {};
        MoveInteraction.Type = DebugInteraction_Move;
        MoveInteraction.P = &Tree->UIP;

        rectangle2 MoveBox = RectCenterHalfDim(Tree->UIP - v2{4.0f, 4.0f}, v2{4.0f, 4.0f});
        if (IsInRectangle(MoveBox, MouseP)) {
            DebugState->NextHotInteraction = MoveInteraction;
        }
        PushRect(&DebugState->RenderGroup, &DebugState->BackingTransform, MoveBox, 0.0f,
                 InteractionsIsHot(DebugState, MoveInteraction) ? v4{1, 0, 0, 1}
                                                                : v4{0.25, 0.25, 0.25, 1});

        EndLayout(&Layout);
    }
#if 0
    uint32 NewHotMenuIndex = ArrayCount(DebugVariableList);
    real32 BestDistanceSq = Real32Maximum;

    real32 MenuRadius = 200.0f;
    real32 AngleStep = Tau32 / (real32)ArrayCount(DebugVariableList);
    for (uint32 MenuItemIndex = 0; MenuItemIndex < ArrayCount(DebugVariableList); ++MenuItemIndex) {
        debug_variable *Var = DebugVariableList + MenuItemIndex;
        const char *Text = Var->Name;
        v4 ItemColor = Var->Value ? v4{1, 1, 1, 1} : v4{0.5f, 0.5f, 0.5f, 0.5f};
        if (MenuItemIndex == DebugState->HotMenuIndex) {
            ItemColor = v4{1, 1, 0, 1};
        }
        real32 Angle = (real32)MenuItemIndex * AngleStep;
        v2 TextP = DebugState->MenuP + MenuRadius * Arm2(Angle);
        real32 ThisDistanceSq = LengthSq(TextP - MouseP);
        if (BestDistanceSq > ThisDistanceSq) {
            NewHotMenuIndex = MenuItemIndex;
            BestDistanceSq = ThisDistanceSq;
        }
        rectangle2 TextBounds = DEBUGGetTextSize(DebugState, Text);
       TextOutAt(TextP - 0.5f * GetDim(TextBounds), Text, ItemColor);
    }
    if (LengthSq(MouseP - DebugState->MenuP) > Square(MenuRadius)) {
        DebugState->HotMenuIndex = NewHotMenuIndex;
    } else {
        DebugState->HotMenuIndex = ArrayCount(DebugVariableList);
    }
#endif
}

internal void DEBUGBeginInteract(debug_state *DebugState, game_input *Input, v2 MouseP) {
    (void)DebugState;
    (void)Input;
    (void)MouseP;
    uint32 FrameOrdinal = DebugState->MostRecentFrameOrdinal;
    if (DebugState->HotInteraction.Type) {
        if (DebugState->HotInteraction.Type == DebugInteraction_AutoModifyVariable)
            switch (DebugState->HotInteraction.Element->Frames[FrameOrdinal]
                        .MostRecentEvent->Event.Type) {
                case DebugType_bool32: {
                    DebugState->HotInteraction.Type = DebugInteraction_ToggleValue;
                } break;
                case DebugType_real32: {
                    DebugState->HotInteraction.Type = DebugInteraction_DragValue;
                } break;
                case DebugType_uint32: {
                    DebugState->HotInteraction.Type = DebugInteraction_DragValue;
                } break;
                case DebugType_int32: {
                    DebugState->HotInteraction.Type = DebugInteraction_DragValue;
                } break;
                case DebugType_v2: {
                } break;
                case DebugType_v3: {
                } break;
                case DebugType_v4: {
                } break;
                case DebugType_OpenDataBlock: {
                    DebugState->HotInteraction.Type = DebugInteraction_ToggleValue;
                } break;
                default: {
                } break;
            }
        switch (DebugState->HotInteraction.Type) {
            case DebugInteraction_TearValue: {
                debug_variable_link *RootGroup =
                    CloneVariableLink(DebugState, DebugState->HotInteraction.Link);
                debug_tree *Tree = AddTree(DebugState, RootGroup, MouseP);
                DebugState->HotInteraction.Type = DebugInteraction_Move;
                DebugState->HotInteraction.P = &Tree->UIP;
            } break;
            case DebugInteraction_Select: {
                if (Input->ShiftDown) {
                    ClearSelection(DebugState);
                }
                AddToSelection(DebugState, DebugState->HotInteraction.ID);
            } break;
            default: {
            } break;
        }
        DebugState->Interaction = DebugState->HotInteraction;
    } else {
        DebugState->Interaction.Type = DebugInteraction_NOP;
    }
}
internal void DEBUGMarkEditedEvent(debug_state *DebugState, debug_event *Event) {
    if (Event) {
        GlobalDebugTable->EditEvent = *Event;
        GlobalDebugTable->EditEvent.GUID =
            GetElementFromEvent(DebugState, Event, 0,
                                DebugElement_AddToGroup | DebugElement_CreateHierarchy)
                ->GUID;
    }
}
internal void DEBUGEndInteract(debug_state *DebugState, game_input *Input, v2 MouseP) {
    (void)DebugState;
    (void)Input;
    (void)MouseP;
    uint32 FrameOrdinal = DebugState->MostRecentFrameOrdinal;
    switch (DebugState->Interaction.Type) {
        case DebugInteraction_ToggleExpansion: {
            debug_view *View = GetOrCreateDebugViewFor(DebugState, DebugState->Interaction.ID);
            View->Collapsible.ExpandedAlways = !View->Collapsible.ExpandedAlways;
        } break;
        case DebugInteraction_SetUInt32: {
            *(uint32 *)DebugState->Interaction.Target = DebugState->Interaction.UInt32;
        } break;
        case DebugInteraction_SetPointer: {
            *(void **)DebugState->Interaction.Target = DebugState->Interaction.Pointer;
        } break;
        case DebugInteraction_ToggleValue: {
            debug_event *Event =
                &DebugState->Interaction.Element->Frames[FrameOrdinal].MostRecentEvent->Event;
            Assert(Event);
            switch (Event->Type) {
                case DebugType_bool32: {
                    Event->Value_bool32 = !Event->Value_bool32;
                } break;
                default: {
                };
            }
            DEBUGMarkEditedEvent(DebugState, Event);
        } break;
        case DebugInteraction_SetElementType: {
            DebugState->Interaction.Element->Type = DebugState->Interaction.DebugType;
        } break;

        case DebugInteraction_None:
        case DebugInteraction_NOP:
        case DebugInteraction_AutoModifyVariable:
        case DebugInteraction_DragValue:
        case DebugInteraction_TearValue:
        case DebugInteraction_Resize:
        case DebugInteraction_Move:
        case DebugInteraction_Select: {
        } break;
    }
    DebugState->Interaction.Type = DebugInteraction_None;
    DebugState->Interaction.Generic = 0;
}

internal void DEBUGInteract(debug_state *DebugState, game_input *Input, v2 MouseP) {
    (void)MouseP;
    v2 dMouseP = MouseP - DebugState->LastMouseP;
#if 0 
    if (Input->MouseButtons[PlatformMouseButton_Right].EndedDown) {
        if (Input->MouseButtons[PlatformMouseButton_Right].HalfTransitionCount > 0) {
            DebugState->MenuP = MouseP;
        }
        DrawDebugMainMenu(DebugState, RenderGroup, MouseP);

    } else if (Input->MouseButtons[PlatformMouseButton_Right].HalfTransitionCount > 0)
#endif

    if (DebugState->Interaction.Type) {
        uint32 FrameOrdinal = DebugState->MostRecentFrameOrdinal;
        // debug_variable_tree *Tree = DebugState->Interaction.Tree;
        v2 *P = DebugState->Interaction.P;
        switch (DebugState->Interaction.Type) {
            case DebugInteraction_DragValue: {
                debug_event *Event = DebugState->Interaction.Element
                                         ? &DebugState->Interaction.Element->Frames[FrameOrdinal]
                                                .MostRecentEvent->Event
                                         : 0;
                switch (Event->Type) {
                    case DebugType_real32: {
                        Event->Value_real32 += 0.1f * dMouseP.y;
                    } break;
                    default: {
                    };
                }
                DEBUGMarkEditedEvent(DebugState, Event);
            } break;

            case DebugInteraction_Resize: {
                *P += v2{dMouseP.x, -dMouseP.y};
                P->x = Maximum(P->x, 10.0f);
                P->y = Maximum(P->y, 10.0f);
            } break;

            case DebugInteraction_Move: {
                *P += v2{dMouseP.x, dMouseP.y};
            } break;
            default: {
            } break;
        }
        for (uint32 TransitionIndex =
                 Input->MouseButtons[PlatformMouseButton_Left].HalfTransitionCount;
             TransitionIndex > 1; --TransitionIndex) {
            DEBUGBeginInteract(DebugState, Input, MouseP);
            DEBUGEndInteract(DebugState, Input, MouseP);
        }
        if (!(Input->MouseButtons[PlatformMouseButton_Left].EndedDown)) {
            DEBUGEndInteract(DebugState, Input, MouseP);
        }
        DebugTextLine("DEBUGInteract");
    } else {
        DebugState->HotInteraction = DebugState->NextHotInteraction;
        for (uint32 TransitionIndex =
                 Input->MouseButtons[PlatformMouseButton_Left].HalfTransitionCount;
             TransitionIndex > 1; --TransitionIndex) {
            DEBUGBeginInteract(DebugState, Input, MouseP);
            DEBUGEndInteract(DebugState, Input, MouseP);
        }
        if (Input->MouseButtons[PlatformMouseButton_Left].EndedDown) {
            DEBUGBeginInteract(DebugState, Input, MouseP);
        }
        DebugTextLine("Not");
    }
#if 0
    if (WasPressed(Input->MouseButtons[PlatformMouseButton_Right])) {
        {
        }
    }
#endif
    DebugState->LastMouseP = MouseP;
}

internal uint64 GetTotalClocks(debug_element_frame *Frame) {
    uint64 Result = 0;
    for (debug_stored_event *Event = Frame->OldestEvent; Event; Event = Event->Next) {
        Result += Event->ProfileNode.Duration;
    }
    return Result;
}

void DrawProfileBars(debug_state *DebugState, debug_id GraphID, rectangle2 ProfileRect, v2 MouseP,
                     debug_profile_node *RootNode, real32 LaneStride, real32 LaneHeight,
                     uint32 DepthRemaining) {
    (void)LaneStride;
    real32 FrameSpan = (real32)(RootNode->Duration);
    real32 PixelSpan = GetDim(ProfileRect).x;
    real32 BaseZ = 100.0f - 10.0f * DepthRemaining;
    real32 Scale = 0.0f;
    if (FrameSpan > 0) {
        Scale = PixelSpan / FrameSpan;
    }

    for (debug_stored_event *StoredEvent = RootNode->FirstChild; StoredEvent;
         StoredEvent = StoredEvent->ProfileNode.NextSameParent) {
        debug_profile_node *Node = &StoredEvent->ProfileNode;
        debug_element *Element = Node->Element;
        Assert(Element);
#if 1

        v3 Color = DebugColorTable[PointerToUint32(Element->GUID) % ArrayCount(DebugColorTable)];
        real32 ThisMinX = ProfileRect.Min.x + Scale * (real32)(Node->ParentRelativeClock);
        real32 ThisMaxX = ThisMinX + Scale * (real32)(Node->Duration);

        uint32 LaneIndex = Node->ThreadOrdinal;
        real32 LaneY = ProfileRect.Max.y - LaneHeight * LaneIndex;
        rectangle2 RegionRect = RectMinMax(V2(ThisMinX, LaneY - LaneHeight),  //
                                           V2(ThisMaxX, LaneY));
        PushRect(&DebugState->RenderGroup, &DebugState->UITransform, RegionRect, BaseZ,
                 v4{Color, 1});
        PushRectOutline(&DebugState->RenderGroup, &DebugState->UITransform, RegionRect,
                        BaseZ + 1.0f, V4(0, 0, 0, 1), 2);

        if (IsInRectangle(RegionRect, MouseP)) {
            tooltip_buffer TextBuffer = AddTooltip(DebugState);
            FormatString(TextBuffer.Size, TextBuffer.Data, "%s: %10llucy", Element->GUID,
                         Node->Duration);
            debug_view *View = GetOrCreateDebugViewFor(DebugState, GraphID);
            DebugState->NextHotInteraction = SetPointerInteraction(
                GraphID, (void **)&View->ProfileGraph.GUID, (void *)Element->GUID);
        }
        if (DepthRemaining) {
            DrawProfileBars(DebugState, GraphID, RegionRect, MouseP, Node, 0, LaneHeight / 2,
                            DepthRemaining - 1);
        }
#endif
    }
}
void DrawProfileIn(debug_state *DebugState, debug_id GraphID, rectangle2 ProfileRect, v2 MouseP,
                   debug_element *RootElement) {
#if 1
    debug_element_frame *RootFrame = RootElement->Frames + DebugState->ViewingFrameOrdinal;
    uint32 LaneCount = DebugState->FrameBarLaneCount;
    real32 LaneHeight = 0.0f;
    if (LaneCount > 0) {
        LaneHeight = GetDim(ProfileRect).y / (real32)LaneCount;
    }
    uint64 TotalClock = GetTotalClocks(RootFrame);
    real32 NextX = ProfileRect.Min.x;
    uint64 RelativeClock = 0;
    for (debug_stored_event *Event = RootFrame->OldestEvent; Event; Event = Event->Next) {
        debug_profile_node *Node = &RootFrame->MostRecentEvent->ProfileNode;
        rectangle2 EventRect = ProfileRect;
        RelativeClock += Node->Duration;
        real32 t = (real32)((real64)RelativeClock / (real64)TotalClock);
        EventRect.Min.x = NextX;
        EventRect.Max.x = (1.0f - t) * ProfileRect.Min.x + t * ProfileRect.Max.x;
        NextX = EventRect.Max.x;
        if (Node) {
            DrawProfileBars(DebugState, GraphID, EventRect, MouseP, Node, LaneHeight, LaneHeight,
                            1);
        }
    }
#endif
}

void DrawFrameBars(debug_state *DebugState, debug_id GraphID, rectangle2 ProfileRect, v2 MouseP,
                   debug_element *RootElement) {
    uint32 FrameCount = ArrayCount(RootElement->Frames);
    if (FrameCount > 0) {
        PushRect(&DebugState->RenderGroup, &DebugState->BackingTransform, ProfileRect, 0.0f,
                 v4{0, 0, 0, 0.25f});
        real32 BarWidth = (GetDim(ProfileRect).x / (real32)FrameCount);
        real32 AtX = ProfileRect.Min.x;
        for (uint32 FrameIndex = 0; FrameIndex < FrameCount; ++FrameIndex) {
            debug_stored_event *RootEvent = RootElement->Frames[FrameIndex].MostRecentEvent;
            if (RootEvent) {
                debug_profile_node *RootNode = &RootEvent->ProfileNode;
                real32 FrameSpan = (real32)(RootNode->Duration);
                real32 PixelSpan = GetDim(ProfileRect).y;
                real32 Scale = 0.0f;
                if (FrameSpan > 0) {
                    Scale = PixelSpan / FrameSpan;
                }
                bool32 HighLight = (FrameIndex == DebugState->ViewingFrameOrdinal);
                real32 HighDim = HighLight ? 1.0f : 0.5f;
                for (debug_stored_event *StoredEvent = RootNode->FirstChild; StoredEvent;
                     StoredEvent = StoredEvent->ProfileNode.NextSameParent) {
                    debug_profile_node *Node = &StoredEvent->ProfileNode;
                    debug_element *Element = Node->Element;
                    Assert(Element);

                    v3 Color = DebugColorTable[PointerToUint32(Element->GUID) %
                                               ArrayCount(DebugColorTable)];
                    real32 ThisMinY =
                        ProfileRect.Min.y + Scale * (real32)(Node->ParentRelativeClock);
                    real32 ThisMaxY = ThisMinY + Scale * (real32)(Node->Duration);

                    rectangle2 RegionRect = RectMinMax(V2(AtX, ThisMinY),  //
                                                       V2(AtX + BarWidth, ThisMaxY));

                    PushRect(&DebugState->RenderGroup, &DebugState->UITransform, RegionRect, 0.0f,
                             v4{HighDim * Color, 1});
                    PushRectOutline(&DebugState->RenderGroup, &DebugState->UITransform, RegionRect,
                                    10.0f, ToV4({1, 1, 0}, 1), 2);

                    if (IsInRectangle(RegionRect, MouseP)) {
                        tooltip_buffer TextBuffer = AddTooltip(DebugState);
                        FormatString(TextBuffer.Size, TextBuffer.Data, "%s: %10llucy",
                                     Element->GUID, Node->Duration);

                        debug_view *View = GetOrCreateDebugViewFor(DebugState, GraphID);
                        DebugState->NextHotInteraction = SetPointerInteraction(
                            GraphID, (void **)&View->ProfileGraph.GUID, (void *)Element->GUID);
                    }
                }
                AtX += BarWidth;
            }
        }
    }
}

struct debug_clock_entry {
    debug_element *Element;
    debug_statistic Stats;
};

void DrawTopClocksList(debug_state *DebugState, debug_id GraphID, rectangle2 ProfileRect, v2 MouseP,
                       debug_element *RootElement) {
    (void)GraphID;
    (void)ProfileRect;
    (void)MouseP;
    (void)RootElement;
    temporary_memory Temp = BeginTemporaryMemory(&DebugState->DebugArena);
    uint32 LinkCount = 0;
    for (debug_variable_link *Link = GetSentinel(DebugState->ProfileGroup)->Next;
         Link != GetSentinel(DebugState->ProfileGroup); Link = Link->Next) {
        ++LinkCount;
    }
    real64 TotalTime = 0.0f;
    debug_element *Element = 0;
    debug_clock_entry *Entries = PushArray(Temp.Arena, LinkCount, debug_clock_entry);
    sort_entry *SortA = PushArray(Temp.Arena, LinkCount, sort_entry, NoClear());
    sort_entry *SortB = PushArray(Temp.Arena, LinkCount, sort_entry, NoClear());
    uint32 Index = 0;
    for (debug_variable_link *Link = GetSentinel(DebugState->ProfileGroup)->Next;
         Link != GetSentinel(DebugState->ProfileGroup); Link = Link->Next, ++Index) {
        Assert(Link->FirstChild == GetSentinel(Link));
        debug_clock_entry *Entry = Entries + Index;
        BeginDebugStatistic(&Entry->Stats);
        sort_entry *Sort = SortA + Index;
        Entry->Element = Link->Element;
        Element = Entry->Element;
        for (debug_stored_event *Event =
                 Element->Frames[DebugState->ViewingFrameOrdinal].OldestEvent;
             Event; Event = Event->Next) {
            uint64 ClocksWithChildren = Event->ProfileNode.Duration;
            uint64 ClocksWithoutChildren =
                ClocksWithChildren - Event->ProfileNode.DurationOfChildren;
            AccumulateStatistic(&Entry->Stats, (real64)ClocksWithoutChildren);
        }
        EndDebugStatistic(&Entry->Stats);
        TotalTime += Entry->Stats.Sum;
        Sort->SortKey = -(real32)Entry->Stats.Sum;
        Sort->Index = Index;
    }
    RadixSort(LinkCount, SortA, SortB);
    v2 At = {ProfileRect.Min.x, ProfileRect.Max.y - GetBaseLine(DebugState)};
    real64 PercentCount = 0.0f;
    if (TotalTime > 0) {
        PercentCount = 100.0f / TotalTime;
    }
    real64 RunningSum = 0.0f;
    for (Index = 0; (Index < LinkCount); ++Index) {
        debug_clock_entry *Entry = Entries + SortA[Index].Index;
        debug_statistic *Stats = &Entry->Stats;
        const char *GUID = Entry->Element->GUID;
        tooltip_buffer TextBuffer = AddTooltip(DebugState);
        const char *Name = 0;
        if (Global_Debug_FullGUIDName) {
            Name = GUID;
        } else {
            Name = DebugParseName(GUID).Name;
        }
        RunningSum += Stats->Sum;
        // rectangle2 TextRect = TextOutAt(At, TextBuffer);
        // TODO
        FormatString(TextBuffer.Size, TextBuffer.Data,
                     const_cast<char *>("%10llu %02.02f%% %4d %s"), (uint64)Stats->Sum,
                     (PercentCount * Stats->Sum), Stats->Count, Name);
        TextOutAt(At, TextBuffer.Data);
        rectangle2 TextRect = DEBUGGetTextSize(DebugState, At, TextBuffer.Data);
        if (IsInRectangle(TextRect, MouseP)) {
            FormatString(TextBuffer.Size, TextBuffer.Data, "accumulative to this point:[%02.02f%%]",
                         (PercentCount * RunningSum));
        }
        if (At.y < ProfileRect.Min.y) {
            break;
        } else {
            At.y -= GetLineAdvance(DebugState);
        }
    }
    EndTemporaryMemory(Temp);
}

void EndRenderGroup(render_group *RenderGroup) {
    //
    (void)RenderGroup;
}
internal void DEBUGEnd(debug_state *DebugState, game_input *Input) {
    TIMED_FUNCTION();
    PushSortBarrier(&DebugState->RenderGroup, true);
    object_transform Flag = DefaultFlatTransform();
    v2 MouseP = Unproject(&DebugState->RenderGroup, &Flag, v2{Input->MouseX, Input->MouseY}).xy;
    debug_frame *MostRecentFrame = DebugState->Frames + DebugState->ViewingFrameOrdinal;
    (void)MostRecentFrame;
    FormatString(DebugState->RootInfoSize, DebugState->RootInfo, "%.02fms %de %dp %dd",
                 MostRecentFrame->WallSecondsElapsed * 1000.0f, MostRecentFrame->StoreEventCount,
                 MostRecentFrame->ProfileBlockCount, MostRecentFrame->DataBlockCount);
    if (DebugState) {
        DebugState->AltUI = Input->MouseButtons[PlatformMouseButton_Right].EndedDown;
        DebugState->MouseTextLayout = BeginLayout(DebugState, MouseP, MouseP);
        DrawTrees(DebugState, MouseP);
        EndLayout(&DebugState->MouseTextLayout);
        DEBUGInteract(DebugState, Input, MouseP);
        DrawTooltips(DebugState);
        EndRenderGroup(&DebugState->RenderGroup);
        ZeroStruct(DebugState->NextHotInteraction);
    }
}

DEBUG_GAME_FRAME_END(DEBUGGameFrameEnd) {
    (void)Input;
    ZeroStruct(GlobalDebugTable->EditEvent);
    GlobalDebugTable->CurrentEventArrayIndex = !GlobalDebugTable->CurrentEventArrayIndex;
    uint64 ArrayIndex_EventIndex =
        AtomicExchangeUInt64(&GlobalDebugTable->EventArrayIndex_EventIndex,
                             (uint64)GlobalDebugTable->CurrentEventArrayIndex << 32);
    uint32 EventArrayIndex = ArrayIndex_EventIndex >> 32LL;
    Assert(EventArrayIndex <= 1);
    uint32 EventCount = ArrayIndex_EventIndex & 0xFFFF'FFFF;

    debug_state *DebugState = (debug_state *)Memory->DebugStorage;

    if (DebugState) {
        game_assets *Assets = DebugGetGameAssets(Memory);

        DEBUGStart(DebugState, Commands, Assets, DEBUGGetMainGenerationID(Memory),
                   (real32)Commands->Width, (real32)Commands->Height);
        CollateDebugRecords(DebugState, EventCount, GlobalDebugTable->Events[EventArrayIndex]);
        DEBUGEnd(DebugState, Input);
    }
}
#endif  // GAME_INTERNAL
